﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK.Graphics.OpenGL;

namespace RayDen.GLPlayground
{
    public class OpenGLManager
    {

        public static class TextureLoaderParameters
        {
            /// <summary>(Debug Aid, should be set to false) If set to false only Errors will be printed. If set to true, debug information (Warnings and Queries) will be printed in addition to Errors.</summary>
            public static bool Verbose = false;

            /// <summary>Always-valid fallback parameter for GL.BindTexture (Default: 0). This number will be returned if loading the Texture failed. You can set this to a checkerboard texture or similar, which you have already loaded.</summary>
            public static uint OpenGLDefaultTexture = 0;

            /// <summary>Compressed formats must have a border of 0, so this is constant.</summary>
            public const int Border = 0;

            /// <summary>false==DirectX TexCoords, true==OpenGL TexCoords (Default: true)</summary>
            public static bool FlipImages = true;

            /// <summary>When enabled, will use Glu to create MipMaps for images loaded with GDI+ (Default: false)</summary>
            public static bool BuildMipmapsForUncompressed = false;

            /// <summary>Selects the Magnification filter for following Textures to be loaded. (Default: Nearest)</summary>
            public static TextureMagFilter MagnificationFilter = TextureMagFilter.Nearest;

            /// <summary>Selects the Minification filter for following Textures to be loaded. (Default: Nearest)</summary>
            public static TextureMinFilter MinificationFilter = TextureMinFilter.Nearest;

            /// <summary>Selects the S Wrapping for following Textures to be loaded. (Default: Repeat)</summary>
            public static TextureWrapMode WrapModeS = TextureWrapMode.Repeat;

            /// <summary>Selects the T Wrapping for following Textures to be loaded. (Default: Repeat)</summary>
            public static TextureWrapMode WrapModeT = TextureWrapMode.Repeat;

            /// <summary>Selects the Texture Environment Mode for the following Textures to be loaded. Default: Modulate)</summary>
            public static TextureEnvMode EnvMode = TextureEnvMode.Modulate;
        }

        public static void LoadFromDisk(string filename, out uint texturehandle, out TextureTarget dimension)
        {
            dimension = (TextureTarget)0;
            texturehandle = TextureLoaderParameters.OpenGLDefaultTexture;
            ErrorCode GLError = ErrorCode.NoError;

            Bitmap CurrentBitmap = null;

            try // Exceptions will be thrown if any Problem occurs while working on the file. 
            {
                CurrentBitmap = new Bitmap(filename);
                if (TextureLoaderParameters.FlipImages)
                    CurrentBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);

                if (CurrentBitmap.Height > 1)
                    dimension = TextureTarget.Texture2D;
                else
                    dimension = TextureTarget.Texture1D;

                GL.GenTextures(1, out texturehandle);
                GL.BindTexture(dimension, texturehandle);

                #region Load Texture
                OpenTK.Graphics.OpenGL.PixelInternalFormat pif;
                OpenTK.Graphics.OpenGL.PixelFormat pf;
                OpenTK.Graphics.OpenGL.PixelType pt;

                if (TextureLoaderParameters.Verbose)
                    Trace.WriteLine("File: " + filename + " Format: " + CurrentBitmap.PixelFormat);

                switch (CurrentBitmap.PixelFormat)
                {
                    case System.Drawing.Imaging.PixelFormat.Format8bppIndexed: // misses glColorTable setup
                        pif = OpenTK.Graphics.OpenGL.PixelInternalFormat.Rgb8;
                        pf = OpenTK.Graphics.OpenGL.PixelFormat.ColorIndex;
                        pt = OpenTK.Graphics.OpenGL.PixelType.Bitmap;
                        break;
                    case System.Drawing.Imaging.PixelFormat.Format16bppArgb1555:
                    case System.Drawing.Imaging.PixelFormat.Format16bppRgb555: // does not work
                        pif = OpenTK.Graphics.OpenGL.PixelInternalFormat.Rgb5A1;
                        pf = OpenTK.Graphics.OpenGL.PixelFormat.Bgr;
                        pt = OpenTK.Graphics.OpenGL.PixelType.UnsignedShort5551Ext;
                        break;
                    /*  case System.Drawing.Imaging.PixelFormat.Format16bppRgb565:
                          pif = OpenTK.Graphics.OpenGL.PixelInternalFormat.R5G6B5IccSgix;
                          pf = OpenTK.Graphics.OpenGL.PixelFormat.R5G6B5IccSgix;
                          pt = OpenTK.Graphics.OpenGL.PixelType.UnsignedByte;
                          break;
      */
                    case System.Drawing.Imaging.PixelFormat.Format24bppRgb: // works
                        pif = OpenTK.Graphics.OpenGL.PixelInternalFormat.Rgb8;
                        pf = OpenTK.Graphics.OpenGL.PixelFormat.Bgr;
                        pt = OpenTK.Graphics.OpenGL.PixelType.UnsignedByte;
                        break;
                    case System.Drawing.Imaging.PixelFormat.Format32bppRgb: // has alpha too? wtf?
                    case System.Drawing.Imaging.PixelFormat.Canonical:
                    case System.Drawing.Imaging.PixelFormat.Format32bppArgb: // works
                        pif = OpenTK.Graphics.OpenGL.PixelInternalFormat.Rgba;
                        pf = OpenTK.Graphics.OpenGL.PixelFormat.Bgra;
                        pt = OpenTK.Graphics.OpenGL.PixelType.UnsignedByte;
                        break;
                    default:
                        throw new ArgumentException("ERROR: Unsupported Pixel Format " + CurrentBitmap.PixelFormat);
                }

                BitmapData Data = CurrentBitmap.LockBits(new System.Drawing.Rectangle(0, 0, CurrentBitmap.Width, CurrentBitmap.Height), ImageLockMode.ReadOnly, CurrentBitmap.PixelFormat);

                if (Data.Height > 1)
                { // image is 2D
                    if (TextureLoaderParameters.BuildMipmapsForUncompressed)
                    {
                        throw new Exception("Cannot build mipmaps, Glu is deprecated.");
                        //  Glu.Build2DMipmap(dimension, (int)pif, Data.Width, Data.Height, pf, pt, Data.Scan0);
                    }
                    else
                        GL.TexImage2D(dimension, 0, pif, Data.Width, Data.Height, TextureLoaderParameters.Border, pf, pt, Data.Scan0);
                }
                else
                { // image is 1D
                    if (TextureLoaderParameters.BuildMipmapsForUncompressed)
                    {
                        throw new Exception("Cannot build mipmaps, Glu is deprecated.");
                        //  Glu.Build1DMipmap(dimension, (int)pif, Data.Width, pf, pt, Data.Scan0);
                    }
                    else
                        GL.TexImage1D(dimension, 0, pif, Data.Width, TextureLoaderParameters.Border, pf, pt, Data.Scan0);
                }

                GL.Finish();
                GLError = GL.GetError();
                if (GLError != ErrorCode.NoError)
                {
                    throw new ArgumentException("Error building TexImage. GL Error: " + GLError);
                }

                CurrentBitmap.UnlockBits(Data);
                #endregion Load Texture

                #region Set Texture Parameters
                GL.TexParameter(dimension, TextureParameterName.TextureMinFilter, (int)TextureLoaderParameters.MinificationFilter);
                GL.TexParameter(dimension, TextureParameterName.TextureMagFilter, (int)TextureLoaderParameters.MagnificationFilter);

                GL.TexParameter(dimension, TextureParameterName.TextureWrapS, (int)TextureLoaderParameters.WrapModeS);
                GL.TexParameter(dimension, TextureParameterName.TextureWrapT, (int)TextureLoaderParameters.WrapModeT);

                GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureLoaderParameters.EnvMode);

                GLError = GL.GetError();
                if (GLError != ErrorCode.NoError)
                {
                    throw new ArgumentException("Error setting Texture Parameters. GL Error: " + GLError);
                }
                #endregion Set Texture Parameters

                return; // success
            }
            catch (Exception e)
            {
                dimension = (TextureTarget)0;
                texturehandle = TextureLoaderParameters.OpenGLDefaultTexture;
                throw new ArgumentException("Texture Loading Error: Failed to read file " + filename + ".\n" + e);
                // return; // failure
            }
            finally
            {
                CurrentBitmap = null;
            }
        }


        public static void LoadFromByteArray(byte[] data, int width, int height, out uint texturehandle)
        {
            var dimension = (TextureTarget)0;
            texturehandle = TextureLoaderParameters.OpenGLDefaultTexture;
            ErrorCode GLError = ErrorCode.NoError;


            try // Exceptions will be thrown if any Problem occurs while working on the file. 
            {

                dimension = TextureTarget.Texture2D;

                GL.GenTextures(1, out texturehandle);
                GL.BindTexture(dimension, texturehandle);

                #region Load Texture
                OpenTK.Graphics.OpenGL.PixelInternalFormat pif;
                OpenTK.Graphics.OpenGL.PixelFormat pf;
                OpenTK.Graphics.OpenGL.PixelType pt;

                pif = OpenTK.Graphics.OpenGL.PixelInternalFormat.Rgb8;
                pf = OpenTK.Graphics.OpenGL.PixelFormat.Bgr;
                pt = OpenTK.Graphics.OpenGL.PixelType.UnsignedByte;

                GL.TexImage2D(dimension, 0, pif, width, height, TextureLoaderParameters.Border, pf, pt, data);
                
                GL.Finish();
                GLError = GL.GetError();
                if (GLError != ErrorCode.NoError)
                {
                    throw new ArgumentException("Error building TexImage. GL Error: " + GLError);
                }

                #endregion Load Texture

                #region Set Texture Parameters
                GL.TexParameter(dimension, TextureParameterName.TextureMinFilter, (int)TextureLoaderParameters.MinificationFilter);
                GL.TexParameter(dimension, TextureParameterName.TextureMagFilter, (int)TextureLoaderParameters.MagnificationFilter);

                GL.TexParameter(dimension, TextureParameterName.TextureWrapS, (int)TextureLoaderParameters.WrapModeS);
                GL.TexParameter(dimension, TextureParameterName.TextureWrapT, (int)TextureLoaderParameters.WrapModeT);

                GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureLoaderParameters.EnvMode);

                GLError = GL.GetError();
                if (GLError != ErrorCode.NoError)
                {
                    throw new ArgumentException("Error setting Texture Parameters. GL Error: " + GLError);
                }
                #endregion Set Texture Parameters

                return; // success
            }
            catch (Exception e)
            {
                dimension = (TextureTarget)0;
                texturehandle = TextureLoaderParameters.OpenGLDefaultTexture;
                throw new ArgumentException("Texture Loading Error: \n" + e);
                // return; // failure
            }

        }

        public static void LoadFromFloatArray(float[] data, int width, int height, out uint texturehandle)
        {
            var dimension = (TextureTarget)0;
            texturehandle = TextureLoaderParameters.OpenGLDefaultTexture;
            ErrorCode GLError = ErrorCode.NoError;


            try // Exceptions will be thrown if any Problem occurs while working on the file. 
            {

                dimension = TextureTarget.Texture2D;

                GL.GenTextures(1, out texturehandle);
                GL.BindTexture(dimension, texturehandle);

                #region Load Texture
                OpenTK.Graphics.OpenGL.PixelInternalFormat pif;
                OpenTK.Graphics.OpenGL.PixelFormat pf;
                OpenTK.Graphics.OpenGL.PixelType pt;

                pif = OpenTK.Graphics.OpenGL.PixelInternalFormat.Rgb32f;
                pf = OpenTK.Graphics.OpenGL.PixelFormat.Bgr;
                pt = OpenTK.Graphics.OpenGL.PixelType.Float;

                GL.TexImage2D(dimension, 0, pif, width, height, TextureLoaderParameters.Border, pf, pt, data);

                GL.Finish();
                GLError = GL.GetError();
                if (GLError != ErrorCode.NoError)
                {
                    throw new ArgumentException("Error building TexImage. GL Error: " + GLError);
                }

                #endregion Load Texture

                #region Set Texture Parameters
                GL.TexParameter(dimension, TextureParameterName.TextureMinFilter, (int)TextureLoaderParameters.MinificationFilter);
                GL.TexParameter(dimension, TextureParameterName.TextureMagFilter, (int)TextureLoaderParameters.MagnificationFilter);

                GL.TexParameter(dimension, TextureParameterName.TextureWrapS, (int)TextureLoaderParameters.WrapModeS);
                GL.TexParameter(dimension, TextureParameterName.TextureWrapT, (int)TextureLoaderParameters.WrapModeT);

                GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureLoaderParameters.EnvMode);

                GLError = GL.GetError();
                if (GLError != ErrorCode.NoError)
                {
                    throw new ArgumentException("Error setting Texture Parameters. GL Error: " + GLError);
                }
                #endregion Set Texture Parameters

                return; // success
            }
            catch (Exception e)
            {
                dimension = (TextureTarget)0;
                texturehandle = TextureLoaderParameters.OpenGLDefaultTexture;
                throw new ArgumentException("Texture Loading Error: \n" + e);
                // return; // failure
            }

        }
    }
}
